# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import api, fields, models, _

import datetime
from dateutil.relativedelta import relativedelta

from odoo.addons import decimal_precision as dp

import logging
_logger = logging.getLogger(__name__)


class AccountStock(models.Model):
    _name = "account.stock"
    _description = "Account Stock"
    _order = "date desc, id desc"

    def _get_default_date(self):
        today = fields.Date.context_today(self)
        return datetime.date(today.year, today.month, 1)
            
    company_id = fields.Many2one('res.company', string='Company', 
        required=True, index=True, default=lambda self: self.env.user.company_id,
        help="Company related to this")

    name = fields.Char(string='Number', required=True, copy=False, default='/')
    ref = fields.Char(string='Reference', copy=False)
    date = fields.Date(required=True, index=True, default=_get_default_date)

    state = fields.Selection([('draft', 'Unposted'), ('posted', 'Posted')], 
      string='Status', required=True, readonly=True, copy=False, default='draft',
      help='')

    product_type = fields.Selection([('material', 'Material'),('goods', 'Goods')], 
        default='goods', required=True,states={'posted': [('readonly', True)]})

    direction = fields.Selection([('in', 'In'),('out', 'out')],
        default='in', required=True, states={'posted': [('readonly', True)]})

    move_type = fields.Selection([
            ('open', 'Open'),
            ('inventory', 'Inventory'),
            ('production', 'Production'),
            ('purchase', 'Purchase'),
            ('sale', 'Sale') ], 
        default='production', required=True, states={'posted': [('readonly', True)]})

    journal_id = fields.Many2one('account.journal',store=True, compute='_compute_account')
    account_id = fields.Many2one('account.account', store=True, compute='_compute_account' )
    opp_account_id = fields.Many2one('account.account', store=True, compute='_compute_account')

    product_id = fields.Many2one('product.product', string="Product", 
        compute='_compute_product_id', store=True, readonly=True)
    line_ids = fields.One2many('account.stock.line', 'stock_id',
        string='Stock Items',
        states={'posted': [('readonly', True)]}, copy=True )

    company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string="Company Currency", readonly=True,
        help='Utility field to express amount currency', store=True)

    amount = fields.Monetary(compute='_compute_amount', store=True, currency_field='company_currency_id')

    @api.multi
    @api.depends('line_ids.product_id')
    def _compute_product_id(self):
        for move in self:
            product_id = move.line_ids.mapped('product_id')
            move.product_id = product_id.id if len(product_id) == 1 else False

    @api.one
    @api.depends('line_ids.amount' )
    def _compute_amount(self):
        self.amount = sum(self.line_ids.mapped('amount') )

    @api.one
    @api.depends('product_type', 'move_type','line_ids.product_id' )
    def _compute_account(self):
        """
        stock account:
        material,...:   material journal debit account, 
        goods,   ...:   goods journal debit account, 
        
        opp account:
        ...,in/out, purchage:    product expense account, 
        ...,in/out, inventory:   goods/material journal credit account, 
        ...,in/out, production:  production journal credit account, 
        ...,in/out, sale:        purchase journal credit account, 
        
        """

        product_type = self.product_type

        journal = self.env['account.journal'].search([
                ('internal_code','=',product_type), ('type','=','stock')],limit=1)
        
        self.journal_id = journal
        self.account_id = journal.default_debit_account_id
        
        if self.move_type == 'inventory':
            opp_journal = journal
            self.opp_account_id = journal.default_credit_account_id

        elif self.move_type == 'production':
            opp_journal = self.env['account.journal'].search([
                ('internal_code','=','production'), ('type','=','stock')],limit=1)
            self.opp_account_id = opp_journal.default_credit_account_id

        elif self.move_type == 'sale':
            opp_journal = self.env['account.journal'].search([('type','=','purchase')],limit=1)
            self.opp_account_id = opp_journal.default_credit_account_id

        elif self.move_type == 'purchase':
            if self.line_ids:
                accounts = self.line_ids[0].product_id.product_tmpl_id.get_product_accounts()
                if accounts['expense']:
                    self.opp_account_id = accounts['expense']

    def _prepare_move(self ):
        
        debit_line_ids = self.line_ids.mapped(lambda item: {
            'account_id': item.account_id.id,
            'product_id': item.product_id.id,
            'debit':  item.amount > 0 and  item.amount or 0,
            'credit': item.amount < 0 and -item.amount or 0,
            'quantity': item.quantity,
        })
        
        credit_line_ids = self.line_ids.mapped(lambda item: {
            'account_id': item.opp_account_id.id,
            'product_id': item.product_id.id,
            'debit':  item.amount < 0 and -item.amount or 0,
            'credit': item.amount > 0 and  item.amount or 0,
            'quantity': -item.quantity,
        })
        
        line_ids = map(lambda item: [0,0,item],debit_line_ids + credit_line_ids)

        return { 'journal_id': self.journal_id.id, 'line_ids': line_ids, }
        
    @api.multi
    def post_open(self ):
        for rec in self:
            if rec.move_type != 'open':
                return None
        
        line_ids = self.mapped('line_ids').mapped(lambda item: {
            'type': 'open',
            'account_id': item.account_id.id,
            'product_id': item.product_id.id,
            'quantity':  item.quantity,
            'debit': item.amount,
        })
        
        line_ids = map(lambda item: [0,0,item], line_ids )
        
        bal = self.env['account.balance'].find_open()
        bal.write( {'line_ids': line_ids } )
        return self.write({'state': 'posted'})


    @api.multi
    def post(self, open=None ):
        if open:
            return self.post_open()
        
        vals_list = [ rec._prepare_move() for rec in self ]
        self.env['account.move'].create(vals_list)
        return self.write({'state': 'posted'})

    @api.multi
    def unpost(self):
        for move in self:
            if not move.journal_id.update_posted:
                raise UserError(_('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))

        if self.ids:
            self.check_access_rights('write')
            self.check_access_rule('write')
            self._cr.execute('UPDATE account_stock '\
                       'SET state=%s '\
                       'WHERE id IN %s', ('draft', tuple(self.ids),))
            self.invalidate_cache()
        return True

    @api.multi
    def get_price(self):
        date = self and self[0].date or None
        query_results = self.get_all_price(date)
        
        if not self:
            return query_results

        product_ids = self.line_ids.mapped('product_id')
        return [ vals for vals in query_results 
                    if vals['product_id'] in product_ids.ids]

    @api.model
    def get_all_price(self, date=None):
        if not date:
            date0 = datetime.date.today()
        elif isinstance( date, str ):
            date0 = datetime.datetime.strptime(date,'%Y-%m-%d').date()
        else:
            date0 = date
        
        #date0 = datetime.datetime.strptime(date,'%Y-%m-%d').date()
        this_month_1st_day = datetime.date(date0.year, date0.month,1)
        
        query = """
WITH args1 as (
    SELECT %s company_id, %s date
--    SELECT 1 company_id, '2019-5-5'::date date

), args as (
    SELECT company_id, 
    date_trunc('month',date)::date as this_month,
    (date_trunc('month',date) + interval '1 month')::date as next_month,
    (date_trunc('month',date) - interval '1 month')::date as last_month,
    (date_trunc('month',date) + interval '1 year' + interval '1 month')::date as next_year_next_month
    FROM args1
), open as (
    SELECT abl1.account_id, asa.product_id, quantity, balance 
    From account_balance_line abl1
    JOIN account_balance ab1
    ON abl1.balance_id = ab1.id 
        and ab1.company_id = (select company_id FROM args)
        and ab1.date = (select this_month FROM args)
    LEFT JOIN account_sub_account asa
    ON asa.id = sub_account_id
   
), stock_in as (
    SELECT stk.account_id, asl.product_id,
      quantity_in - quantity_out quantity,
      asl.amount
    FROM account_stock_line asl
    JOIN account_stock stk
    on asl.stock_id = stk.id
    and stk.state = 'posted'
    and ( stk.move_type in ('purchase','inventory')
        or ( stk.move_type in ('production','sale') and stk.product_type = 'goods' and stk.direction = 'in') 
        )
        and stk.company_id = (select company_id FROM args)
        and stk.date >= (select this_month FROM args)
        and stk.date < (select next_month FROM args)

    order by 1,2
), all_data as (
    SELECT account_id, product_id, 
      sum(quantity) quantity, 
      sum(balance) amount
    FROM (
      SELECT * from open
      UNION ALL SELECT --*
         account_id,  product_id, quantity, amount
       from stock_in
    ) aa
    GROUP BY account_id, product_id
    order by 1,2
), price as (
    SELECT dd.account_id, aa.code account_code, aa.name account_name, 
      product_id, pp.default_code product_code, pt.name product_name,
      quantity, amount,
      case when quantity = 0 then 0 else amount / quantity end price
    FROM all_data dd
    LEFT JOIN account_account aa ON dd.account_id = aa.id
    LEFT JOIN product_product pp ON dd.product_id = pp.id
    LEFT JOIN product_template pt ON pp.product_tmpl_id = pt.id

    order by 1,2
)

select * from price



        """
        
        self.env.cr.execute(query,( self.env.user.company_id.id, this_month_1st_day))
        query_results = self.env.cr.dictfetchall()
        
        for index, val in enumerate( query_results):
            val['id'] = index

        """
        {
        'account_id': '1403',   # 库存产品和原材料 才有用，其他的是多余的
        'account_code': '', # 
        'account_name': '', # 
        'product_id': '', # 
        'product_code': '', # 
        'product_name': '', # 
        'quantity': 0,
        'amount': 0,
        'price':    # 不要用价格。要用 amount / quantity
        }
        """
        
        return query_results

    @api.multi
    def set_out_amount(self):
        prices = self.get_price()
        
        prices = dict(((price['account_id'], price['product_id']), 
                        price ) for price in prices )
        
        for rec in self:
            for line in rec.line_ids:
                price = prices[ ( line.account_id.id, line.product_id.id )]
                amount = price['amount']
                quantity = price['quantity']
                if quantity:
                    line.amount = amount * 1.0 / quantity * line.quantity
        
        return True


class AccountStockLine(models.Model):
    
    _name = "account.stock.line"
    _description = "Account Stock Line"
    _order = "date desc, id desc"

    name = fields.Char(string='Label', required=True, default='.')
    stock_id = fields.Many2one('account.stock', required=True,ondelete="cascade")
    company_id = fields.Many2one('res.company', related='stock_id.company_id', string='Company', store=True, readonly=True)
    date = fields.Date(related='stock_id.date')
    product_type = fields.Selection( store=True, related='stock_id.product_type' )
    direction = fields.Selection( store=True, related='stock_id.direction' )
    move_type = fields.Selection( store=True, related='stock_id.move_type' )

    journal_id = fields.Many2one('account.journal',store=True,related='stock_id.journal_id')
    account_id = fields.Many2one('account.account', store=True,related='stock_id.account_id' )
    opp_account_id = fields.Many2one('account.account', store=True, compute='_compute_account')

    product_id = fields.Many2one( 'product.product', string='Product',required=True )

    @api.one
    @api.depends('product_id' )
    def _compute_account(self):
        if self.move_type in ['purchase']:
            accounts = self.product_id.product_tmpl_id.get_product_accounts()
            if accounts['expense']:
                self.opp_account_id = accounts['expense']
                
        else:
            self.opp_account_id = self.stock_id.opp_account_id


    quantity_in  = fields.Float(digits=dp.get_precision('Product Unit of Measure') )
    quantity_out = fields.Float(digits=dp.get_precision('Product Unit of Measure') )
    quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'),
        store=True, compute='_compute_quantity' )

    company_currency_id = fields.Many2one('res.currency', related='company_id.currency_id', string="Company Currency", readonly=True,
        help='Utility field to express amount currency', store=True)

    price = fields.Float(string='Unit Price', digits=dp.get_precision('Product Price'),
        compute='_compute_price', inverse='_inverse_price')
        
    amount = fields.Monetary( default=0.00, currency_field='company_currency_id')

    @api.depends('quantity_in', 'quantity_out')
    def _compute_quantity(self):
        for line in self:
            line.quantity  = line.quantity_in - line.quantity_out


    @api.depends('amount', 'quantity_in', 'quantity_out')
    def _compute_price(self):
        for line in self:
            quantity = line.quantity_in - line.quantity_out
            line.price  = quantity and line.amount * 1.00 / quantity or 0.00

    def _inverse_price(self):
        for line in self:
            quantity = line.quantity_in - line.quantity_out
            line.amount  = quantity * line.price

    @api.model
    def create(self, vals):
        vals = self._check_vals(vals)
        return create2(AccountStockLine,self, vals )

    @api.multi
    def write(self, vals):
        vals = self._check_vals(vals)
        return write2(AccountStockLine,self, vals )

    def _check_vals(self, vals):
        amount = vals.get('amount', None)
        
        if not amount == None:
            del vals['price']
        
        return vals
